const ts = require('ohos-typescript');
const fs = require('fs');
const { Sdk, Project } = require('./utils');
const tsConfig = require('./tsconfig.json');

class Counter {
    constructor(args) {
        this.sdk = args['sdk'] ? new Sdk(args['sdk']) : this.reportErr('Please set sdk path.')
        this.project = args['app'] ? new Project(args['app']) : this.reportErr('Please set app path.')
        this.output = args['output'] ? fs.createWriteStream(args['output'], { encoding: 'utf-8', options: 'r+'}) : undefined;
    }

    getProgram() {
        this.apiLibs = this.sdk.getSdkLibs('api');
        this.componentLibs = this.sdk.getSdkLibs('component');
        this.kitLibs = this.sdk.getSdkLibs('kits');
        this.arktsLibs = this.sdk.getSdkLibs('arkts');
        this.depLibs = this.project.getDepLibs();
        this.appSourceSet = this.project.getAppSources();
        
        const etsConfig = tsConfig.compilerOptions.ets;
        const compilerOption = {
            moduleResolution: ts.ModuleResolutionKind.NodeJs,
            target: ts.ScriptTarget.ESNext,
            ets: etsConfig,
            allowJs: false,
            lib: [...this.apiLibs, ...this.componentLibs],
            baseUrl: '.',
            paths: {
                "@*": this.depLibs
            }
        };

        this.compilerHost = ts.createCompilerHost(compilerOption);
        this.compilerHost.resolveModuleNames = this.getResolveModuleNames();

        return ts.createProgram({
            rootNames: [...this.appSourceSet],
            options: compilerOption,
            host: this.compilerHost
        });
    }

    getResolveModuleNames() {
        return (moduleNames, containingFile, reusedNames, redirectedReference, options) => {
            const resolvedModules = [];
            for (const moduleName of moduleNames) {
                const moduleLookupLocation = ts.resolveModuleName(moduleName, containingFile, options, {
                    fileExists: (fileName) => {
                        return fileName && ts.sys.fileExists(fileName);
                    },
                    readFile: (fileName) => {
                        ts.sys.readFile(fileName);
                    },
                });
                if (moduleLookupLocation.resolvedModule) {
                    resolvedModules.push(moduleLookupLocation.resolvedModule);
                } else {
                    const modulePath = this.resolveModuleName(moduleName);
                    const resolved = modulePath && ts.sys.fileExists(modulePath) ? {resolvedFileName: modulePath} : undefined;
                    resolvedModules.push(resolved);
                }
            }
            return resolvedModules;
        }
    }

    resolveModuleName(moduleName) {
        const name = `${moduleName}.d.ts`;
        if (moduleName.startsWith('@ohos.')) {
            for (const lib of this.apiLibs) {
                if (lib.endsWith(name)) {
                    return lib;
                }
            }
        }
        if (moduleName.startsWith('@kit.')) {
            for (const lib of this.kitLibs) {
                if (lib.endsWith(name)) {
                    return lib;
                }
            }
        }
        if (moduleName.startsWith('@arkts.')) {
            for (const lib of this.arktsLibs) {
                if (lib.endsWith(name)) {
                    return lib;
                }
            }
        }
        return undefined;
    }

    count() {
        console.log('=====================count start=========================')
        const program = this.getProgram();
        const typeChecker = program.getTypeChecker();
        const output = this.output;
        let sourceFileName = '';

        if (output) {
            output.write("FuncCalls; Parent; SrcFile\n");
        }

        for (const fileName of this.appSourceSet) {
            if (!output) {
                console.log(`\n源文件：${fileName}`);
            }
            sourceFileName = fileName;
            const sourceFile = program.getSourceFile(fileName);
            ts.forEachChild(sourceFile, visit);
        }

        function visit(node) {
            if (ts.isImportDeclaration(node)) {
                const symbol = typeChecker.getSymbolAtLocation(node.moduleSpecifier);
                if (symbol) {
                    typeChecker.getExportsOfModule(symbol);
                }
            }

            if (ts.isCallExpression(node)) {
                const expression = node.expression;
                if (ts.isPropertyAccessExpression(expression)) {
                    const typeName = typeChecker.getTypeAtLocation(expression.expression);
                    const functionName = expression.name.getText();
                    if (output) {
                        output.write(`${functionName}; ${typeChecker.typeToString(typeName)}; ${sourceFileName}\n`);
                    } else {
                        console.log(`${`函数调用 ${functionName}`.padEnd(50, ' ')} 属于类 ${typeChecker.typeToString(typeName)}`);
                    }
                } else {
                    if (output) {
                        output.write(`${expression.getText()};;${sourceFileName}\n`);
                    } else {
                        console.log(`函数调用 ${expression.getText()}`);
                    }
                }
            }
            
            ts.forEachChild(node, visit);
        }

        console.log('======================count end==========================')
    }

    reportErr(msg) {
        console.log(msg);
        process.exit();
    }
}

exports.Counter = Counter;
